#pragma once

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <ctime>
#include <cstdarg>
#include <fstream>
#include <cstring>
#include <pthread.h>
#include "LockGuard.hpp"

namespace log_ns
{

    enum//日志等级
    {
        DEBUG = 1,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };
    //将日志等级转为字符串
    std::string LevelToString(int level)
    {
        switch (level)
        {
        case DEBUG:
            return "DEBUG";
        case INFO:
            return "INFO";
        case WARNING:
            return "WARNING";
        case ERROR:
            return "ERROR";
        case FATAL:
            return "FATAL";
        default:
            return "UNKNOWN";
        }
    }
    //获取当前时间
    std::string GetCurrTime()
    {
        time_t now = time(nullptr);//该函数可获取此时刻时间戳
        //localtime通过时间戳可转化为当前的时间的年月日时分秒
        struct tm *curr_time = localtime(&now);
        char buffer[128];
        snprintf(buffer, sizeof(buffer), "%d-%02d-%02d %02d:%02d:%02d",
                 curr_time->tm_year + 1900,
                 curr_time->tm_mon + 1,
                 curr_time->tm_mday,
                 curr_time->tm_hour,
                 curr_time->tm_min,
                 curr_time->tm_sec);
        return buffer;
    }

    class logmessage
    {
    public:
        std::string _level;//等级
        pid_t _id;//进程的id
        std::string _filename;//文件名
        int _filenumber;//文件编号
        std::string _curr_time;//时间
        std::string _message_info;//日志内容
    };

#define SCREEN_TYPE 1 //向显示器打印
#define FILE_TYPE 2   //往文件写
    //给个缺省的文件名方便测试
    const std::string glogfile = "./log.txt";
    //保证在多线程模式下，日志资源的安全（上锁）
    pthread_mutex_t glock = PTHREAD_MUTEX_INITIALIZER;

    // log.logMessage("", 12, INFO, "this is a %d message ,%f, %s hellwrodl", x, , , );
    class Log
    {
    public:
        Log(const std::string &logfile = glogfile) : _logfile(logfile), _type(SCREEN_TYPE)
        {
        }
        //调用该函数可以选择向哪里打印
        void Enable(int type)
        {
            _type = type;
        }
        void FlushLogToScreen(const logmessage &lg)
        {//向显示器打印的方法
            printf("[%s][%d][%s][%d][%s] %s",
                   lg._level.c_str(),
                   lg._id,
                   lg._filename.c_str(),
                   lg._filenumber,
                   lg._curr_time.c_str(),
                   lg._message_info.c_str());
        }
        void FlushLogToFile(const logmessage &lg)
        {//向文件打印的方法
            //文件操作的接口，std::ios::app表“append”模式
            //所有的输出都会被追加到文件的末尾，而不是覆盖文件的现有内容。
            std::ofstream out(_logfile, std::ios::app);
            if (!out.is_open())
                return;
            char logtxt[2048];
            snprintf(logtxt, sizeof(logtxt), "[%s][%d][%s][%d][%s] %s",
                     lg._level.c_str(),
                     lg._id,
                     lg._filename.c_str(),
                     lg._filenumber,
                     lg._curr_time.c_str(),
                     lg._message_info.c_str());
            out.write(logtxt, strlen(logtxt));
            out.close();
        }
        void FlushLog(const logmessage &lg)
        {
            //对日志做保护本质是对打印的资源做保护
            LockGuard lockguard(&glock);
            switch (_type)//选择向哪里打印
            {
            case SCREEN_TYPE:
                FlushLogToScreen(lg);
                break;
            case FILE_TYPE:
                FlushLogToFile(lg);
                break;
            }
        }
        //记录的日志信息 
        void logMessage(std::string filename, int filenumber, int level, const char *format, ...)
        {
            logmessage lg;

            lg._level = LevelToString(level);
            lg._id = getpid();//当前进程自己的pid
            lg._filename = filename;
            lg._filenumber = filenumber;
            lg._curr_time = GetCurrTime();
            
            
            //捕获可变参数c语言的处理方法
            va_list ap;
            //可变参初始化
            va_start(ap, format);
            //取出可变参数
            char log_info[1024];
            //把可变参变为转为字符串存放在log_info
            vsnprintf(log_info, sizeof(log_info), format, ap);
            //销毁
            va_end(ap);
            lg._message_info = log_info;

            // 打印出来日志
            FlushLog(lg);
        }
        ~Log()
        {
        }

    private:
        int _type;//表示向什么设备上打印
        std::string _logfile;//如果向文件里打印，那么就要接收文件路径
    };
//包含Log.hpp就能直接使用了，对外直接使用下面的三个接口就行了
    Log lg;

//使用宏封装调用接口，__FILE__ 和 __LINE__ 是两个预定义的宏，
//它们分别用于在编译时提供当前源文件的名称和当前行号。 
#define LOG(Level, Format, ...)                                   \
    do                                                                 \
    {                                                                  \
        lg.logMessage(__FILE__, __LINE__, Level, Format, ##__VA_ARGS__); \
    } while (0)
//向显示器打印
#define EnableScreen()          \
    do                          \
    {                           \
        lg.Enable(SCREEN_TYPE); \
    } while (0)
//向文件打印
#define EnableFILE()          \
    do                        \
    {                         \
        lg.Enable(FILE_TYPE); \
    } while (0)
};